问题
最近在做一个项目时,遇到了这样一个问题:网页大标题要用设计师指定的中文字体,该字体文件比较大,浏览器加载字体文件的过程中是不会显示使用该字体的文本的,于是出现了初次打开网页时有一段时间“No title”的BUG。
解决方案
针对该问题,笔者能想到以下几种解决方案(欢迎补充):
- 把文字做成png图片,或改为路径后做成SVG图片,考虑做成base64内嵌到CSS文件。
- 使用字体工具,将字体包中没用到的字符删除来减小字体文件的体积(npm上也有这样的优秀JS库)。
- 方案1的升级版,在所有用到这个字体的文本中,把首屏出现的文本做成图片,后面的文本依然使用字体包,并在打开首页时就加载字体包。
- 方案2的升级版,在向服务器请求字体的时候,由服务端分析document中所有用到的字符,动态生成字体包后返回给浏览器端。
- 在loading字体的过程中,先用一种最接近目标字体的安全字体来显示,等字体文件加载完后进行替换。
- 打死设计师,想用啥用啥。
方案对比与选择
- 由于后续的页面内容也有不少文本会用到该字体,且考虑今后网站维护成本,所以1、2两个解决方案不适合本项目。
- 第3个方案最省事,做快速开发的话比较合适,但代码复用性不高,程序也不够健壮,例如低网速情况下,有可能会出现字体包未完全加载时用户已经滑到下一页,而这一页中有文本是使用了目标字体包从而不显示的情况。
- 第4个方案需要后端开发的配合,要考虑如何判断所有用到的字符,并且在JS向document中写入新的字符时,要请求增量字体包,会较大程度地增加CDN服务端负担(主要是怕跟后端开发撕逼)。
- 第5个方案是一种迫不得已的选择,在用户眼皮底下更换字体,是非常影响体验的,好处是字体属于异步加载,不会阻塞文本显示。
- 最后一个方案成本比较高,需要搭上开发人员的下半辈子,好处是可以从根本上解决这个BUG,永诀后患。被逼急的程序员可以尝试下。
结合项目特点,最终选择方案5。
实现过程
整个过程逻辑非常简单:首先标题所用的class在CSS中被定义了一个最接近目标字体的安全字体,然后等待字体文件加载,加载完成后将标题的class换成自定义字体的class。
首先设好两个CSS属性,一个是用最接近目标字体的安全字体,用于默认字体,第二个是自定义字体:
/* 定义字体 */
@font-face{
font-family: Lanting_light;
src: url("../res/font/goDieDesigner.woff");
}
/* 安全字体,用于默认 */
.lanting_l{
font-family: Arial, Helvetica, sans-serif;
}
/* 自定义字体 */
.lanting_light{
font-family: "Lanting_light",Arial, Helvetica, sans-serif;
}
元素设置成默认字体:
<p class="Lanting_l">
我是一段可爱的文字,啾咪~
</p>
写一个函数,用于替换元素的class,在字体文件加载完成后执行它:
function onLoadedFont() {
ele = document.getElementsByClassName("Lanting_l");
for (let i = 0; i < ele.length; i++){
ele[i].classList.add("Lanting_light");
ele[i].classList.remove("Lanting_l");
}
}
接下来是整个问题的核心:如何判断字体文件已被加载?
一个html元素,可以用监听load事件来判断加载,但字体不是html元素,无法监听。
再来看一下需求,字体文件是从用户打开页面时就要开始加载了,所以只要让字体一开始就加载,然后监听window.load事件就好了:
window.addEventListener("load",onLoadedFont);
PS:这里不太严谨,可能会有其他大体积资源加载拖慢load事件,所以大文件最好用lazyload。MDN提供了一个属性可以判断字体加载,但是目前兼容性还有一些问题,这里就先不用了,感兴趣的可以看一下这里。小弟才疏学浅,如有大神知道其他监听字体文件加载的方法,还请留言告知,谢谢~
然后就是要做些什么让字体文件一开始就去加载了,要知道不同浏览器什么时候会去加载一个字体,可以参考这篇文章。
在HTML中创建一个文本标签,让它去用这个需要被加载的字体,并且让这个文本不要出现在视窗中:
<h1 class="Lanting_light" style="position: fixed;left:calc(-1000% - 5000px)">字体</h1>
最后擦个屁股,load完成后把上述已经被榨干剩余价值的节点删掉:
window.addEventListener("load",function(){
let dev = document.getElementsByClassName("Lanting_light");
for (let i = 0; i < dev.length; i++){
dev[i].parentElement.removeChild(dev[i]);
}
//必须先删除旧节点后再修改需要改的节点,否则文本节点会被删掉
onLoadedFont();
});
到这里,整个功能就完成了。
总结
总结一下,这个方案可以让字体文件没有加载完的时候,先用一个接近的安全字体让文本先显示出来,待字体加载完后再换字体,核心的点是监听字体文件的加载,因为MDN上提供的document.font是一个实验性功能,兼容性不是很好,所以这里用了投机取巧的办法去监听window.load,可能会被其他大文件阻塞,也会拖慢监听window.load的其他函数,所以在项目中还是要取舍,没有完美的方案,只有最合适的方案。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。